<?php

declare(strict_types=1);

namespace Common;

use App\Repository\ProjectConfigRepository;
use Hyperf\AsyncQueue\Driver\DriverFactory;
use Hyperf\AsyncQueue\Job;
use Hyperf\Context\ApplicationContext;
use Hyperf\Coroutine\Coroutine;
use Hyperf\Logger\LoggerFactory;
use Hyperf\Redis\Redis;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;

class Common
{
    public static function getConfig(string $key, string $default = ''): string
    {
        return self::container()->get(ProjectConfigRepository::class)
            ->findByKey($key) ?? $default;
    }

    public static function container(): ContainerInterface
    {
        return ApplicationContext::getContainer();
    }

    public static function getConfigByGroup(string $groupCode): array
    {
        $data = self::container()->get(ProjectConfigRepository::class)
            ->getQuery()->where(['group_code' => $groupCode])
            ->get()->keyBy('key');
        if (!$data) {
            return [];
        }
        return $data->toArray();
    }

    public static function setConfig(string $key, string $value): bool
    {
        return self::container()->get(ProjectConfigRepository::class)
            ->updateByKey($key, $value);
    }

    public static function redis(): Redis
    {
        return self::container()->get(Redis::class);
    }

    public static function log(string $name, string $message = '', string $level = LogLevel::INFO)
    {
        $logger = self::container()->get(LoggerFactory::class)->get($name);
        $logger->$level($message);
    }

    public static function logException(string $name, \Throwable $e, string $extra = '')
    {
        $message = $e->getFile() . ':' . $e->getLine() . ' message:' . $e->getMessage() . (empty($extra) ? '' : ' extra:' . $extra);
        self::log($name, $message, LogLevel::ERROR);
    }

    public static function getLogger(string $name): LoggerInterface
    {
        return self::container()->get(LoggerFactory::class)->get($name);
    }

    // JWT生成
    public static function generateJwt(array $payload, int $expire = 0): string
    {
        if ($expire > 0) {
            $payload['exp'] = time() + $expire; // 添加过期时间
        }
        $header = json_encode(['typ' => 'JWT', 'alg' => 'HS256']);
        $payload = json_encode($payload);
        $secret = env('JWT_SECRET');

        $base64UrlHeader = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($header));
        $base64UrlPayload = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($payload));

        $signature = hash_hmac('sha256', "$base64UrlHeader.$base64UrlPayload", $secret, true);
        $base64UrlSignature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature));

        return "Bearer $base64UrlHeader.$base64UrlPayload.$base64UrlSignature";
    }

    // 添加JWT解析方法
    public static function parseJwt(string $jwt): array|false
    {
        $data = explode(' ', $jwt);
        if (count($data) !== 2 || $data[0] !== 'Bearer') {
            return false;
        }
        $jwt = $data[1];
        $parts = explode('.', $jwt);
        if (count($parts) !== 3) {
            return false;
        }

        [$base64UrlHeader, $base64UrlPayload, $base64UrlSignature] = $parts;

        $header = json_decode(base64_decode(str_replace(['-', '_'], ['+', '/'], $base64UrlHeader)), true);
        $payload = json_decode(base64_decode(str_replace(['-', '_'], ['+', '/'], $base64UrlPayload)), true);
        $secret = env('JWT_SECRET');

        $signature = hash_hmac('sha256', "$base64UrlHeader.$base64UrlPayload", $secret, true);
        $base64UrlExpectedSignature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature));
        if ($base64UrlSignature !== $base64UrlExpectedSignature) {
            return false;
        }

        // 验证过期时间
        if (isset($payload['exp']) && $payload['exp'] < time()) {
            return false;
        }

        return $payload;
    }

    public static function lock(string $key, mixed $val = 1, int $expire = 5, int $retry = 3, float $interval = 0.5): bool
    {
        for ($i = 0; $i < $retry; $i++) {
            if (self::redis()->set($key, $val, ['nx', 'ex' => $expire])) {
                return true;
            }
            Coroutine::sleep($interval);
        }
        return false;
    }

    public static function renewLock(string $key, mixed $val = 1, int $expire = 5): bool
    {
        return self::redis()->setex($key, $expire, $val);
    }

    public static function unlock(string $key, mixed $val = 1)
    {
        if (self::redis()->get($key) != $val) {
            return 0;
        }
        return self::redis()->del($key);
    }

    public static function push_redis_job(Job $job, string $queue = 'default', int $delay = 0)
    {
        $driver = self::container()->get(DriverFactory::class)->get($queue);
        return $driver->push($job, $delay);
    }
    
    /**
     * AES加密
     * @param string $data 要加密的数据
     * @param string $key 密钥
     * @param string $method 加密方法，默认AES-256-CBC
     * @return string 返回base64编码的加密数据
     */
    public static function aesEncrypt(string $data, string $key, string $method = 'AES-256-CBC'): string
    {
        $ivLength = openssl_cipher_iv_length($method);
        $iv = openssl_random_pseudo_bytes($ivLength);
        $encrypted = openssl_encrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv);
        return base64_encode($iv . $encrypted);
    }

    /**
     * AES解密
     * @param string $data 要解密的数据（base64编码）
     * @param string $key 密钥
     * @param string $method 解密方法，默认AES-256-CBC
     * @return string|false 解密后的数据，失败返回false
     */
    public static function aesDecrypt(string $data, string $key, string $method = 'AES-256-CBC'): string|false
    {
        // 解码base64数据
        $decodedData = base64_decode($data, true);
        if ($decodedData === false) {
            return false;
        }
        $ivLength = openssl_cipher_iv_length($method);
        // 提取IV和加密数据
        $iv = substr($decodedData, 0, $ivLength);
        $encrypted = substr($decodedData, $ivLength);
        
        // 执行解密
        $decrypted = openssl_decrypt($encrypted, $method, $key, OPENSSL_RAW_DATA, $iv);
        
        // 返回解密结果
        return $decrypted !== false ? $decrypted : false;
    }

    /**
     * 保存Base64编码的图片文件
     *
     * @param string $base64Data Base64编码的图片数据
     * @param string $savePath 文件保存路径（不包含文件名）
     * @param string $originalName 原始文件名（可选）
     * @return array 包含完整文件路径和文件名的数组
     */
    public static function saveBase64Image(string $base64Data, string $savePath, string $originalName = ''): array
    {
        // 清理base64数据
        $cleanBase64Data = self::cleanBase64Data($base64Data);
        
        // 生成文件名
        $fileName = self::generateFileNameFromBase64($cleanBase64Data, $originalName);
        
        // 确保目录存在
        if (!is_dir($savePath)) {
            mkdir($savePath, 0755, true);
        }
        
        // 完整文件路径
        $fullPath = $savePath . '/' . $fileName;
        
        // 保存文件
        $imageData = base64_decode($cleanBase64Data);
        file_put_contents($fullPath, $imageData);
        
        return [
            'path' => $fullPath,
            'name' => $fileName
        ];
    }

    /**
     * 清理base64数据，移除可能存在的前缀
     *
     * @param string $base64Data
     * @return string
     */
    public static function cleanBase64Data(string $base64Data): string
    {
        // 移除可能存在的 data URI 前缀，例如: data:image/png;base64,...
        if (strpos($base64Data, 'data:') === 0) {
            $base64Data = substr($base64Data, strpos($base64Data, ',') + 1);
        }
        
        // 移除换行符和空格
        return trim($base64Data);
    }

    /**
     * 根据base64数据生成文件名
     *
     * @param string $base64Data
     * @param string $originalName
     * @return string
     */
    public static function generateFileNameFromBase64(string $base64Data, string $originalName = ''): string
    {
        // 获取文件扩展名
        $extension = self::getFileExtensionFromBase64($base64Data);
        
        // 如果原始文件名不为空，尝试使用原始文件名（但确保扩展名正确）
        if (!empty($originalName)) {
            $pathInfo = pathinfo($originalName);
            $basename = $pathInfo['filename'] ?? uniqid();
            return $basename . '.' . $extension;
        }
        
        // 否则生成唯一的文件名
        return uniqid('img_') . '.' . $extension;
    }

    /**
     * 从base64数据中获取文件扩展名
     *
     * @param string $base64
     * @return string
     */
    public static function getFileExtensionFromBase64(string $base64): string
    {
        $mime = self::getMimeTypeFromBase64($base64);
        
        return match ($mime) {
            'image/jpeg', 'image/jpg' => 'jpg',
            'image/png' => 'png',
            'image/gif' => 'gif',
            'image/webp' => 'webp',
            'image/bmp' => 'bmp',
            default => 'jpg', // 默认使用jpg
        };
    }

    /**
     * 从base64数据中获取MIME类型
     *
     * @param string $base64
     * @return string
     */
    public static function getMimeTypeFromBase64(string $base64): string
    {
        // 只取前100个字符用于检测MIME类型，避免内存消耗过大
        $data = base64_decode(substr($base64, 0, 100));
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $mime = finfo_buffer($finfo, $data);
        finfo_close($finfo);
        
        return $mime ?: 'image/jpeg'; // 默认MIME类型
    }

    /**
     * 向Redis Stream添加消息
     *
     * @param string $streamKey Stream键名
     * @param array $message 消息内容
     * @param string|null $customId 自定义消息ID，为null时由Redis生成
     * @return string 返回消息ID
     */
    public static function redisStreamAdd(string $streamKey, array $message, ?string $customId = null): string
    {
        $data = [];
        foreach ($message as $key => $value) {
            $data[$key] = is_scalar($value) ? (string)$value : json_encode($value);
        }
        
        if ($customId) {
            return self::redis()->xAdd($streamKey, $customId, $data);
        } else {
            return self::redis()->xAdd($streamKey, '*', $data);
        }
    }

    /**
     * 从Redis Stream消费消息（不确认）
     *
     * @param string $streamKey Stream键名
     * @param string $consumerGroup 消费者组名
     * @param string $consumerName 消费者名
     * @param int $count 消费消息数量
     * @param int $block 阻塞时间（毫秒），0表示不阻塞
     * @return array 返回消息列表
     */
    public static function redisStreamConsume(string $streamKey, string $consumerGroup, string $consumerName, int $count = 1, int $block = 0): array
    {
        if ($block > 0) {
            return self::redis()->xReadGroup($consumerGroup, $consumerName, [$streamKey => '>'], $count, $block);
        } else {
            return self::redis()->xReadGroup($consumerGroup, $consumerName, [$streamKey => '>'], $count);
        }
    }

    /**
     * 确认已消费的消息
     *
     * @param string $streamKey Stream键名
     * @param string $consumerGroup 消费者组名
     * @param array $messageIds 要确认的消息ID列表
     * @return int 返回确认成功的消息数
     */
    public static function redisStreamAck(string $streamKey, string $consumerGroup, array $messageIds): int
    {
        return self::redis()->xAck($streamKey, $consumerGroup, $messageIds);
    }

    /**
     * 创建消费者组
     *
     * @param string $streamKey Stream键名
     * @param string $consumerGroup 消费者组名
     * @param string $startId 开始ID，默认为$（最新消息）
     * @return bool 成功返回true，失败返回false
     */
    public static function redisStreamCreateGroup(string $streamKey, string $consumerGroup, string $startId = '$'): bool
    {
        try {
            self::redis()->xGroup('CREATE', $streamKey, $consumerGroup, $startId, true);
            return true;
        } catch (\Exception $e) {
            // 组可能已存在，尝试另一种方式创建
            try {
                self::redis()->xGroup('CREATE', $streamKey, $consumerGroup, $startId);
                return true;
            } catch (\Exception $e2) {
                return false;
            }
        }
    }

    /**
     * 删除消费者组
     *
     * @param string $streamKey Stream键名
     * @param string $consumerGroup 消费者组名
     * @return int 返回删除的消费者组数量
     */
    public static function redisStreamDeleteGroup(string $streamKey, string $consumerGroup): int
    {
        return self::redis()->xGroup('DESTROY', $streamKey, $consumerGroup);
    }

    /**
     * 删除已消费的消息
     *
     * @param string $streamKey Stream键名
     * @param array $messageIds 消息ID列表
     * @return int 返回删除成功的消息数
     */
    public static function redisStreamDeleteMessages(string $streamKey, array $messageIds): int
    {
        return self::redis()->xDel($streamKey, $messageIds);
    }

    /**
     * 获取Stream信息
     *
     * @param string $streamKey Stream键名
     * @return array 返回Stream信息
     */
    public static function redisStreamInfo(string $streamKey): array
    {
        return self::redis()->xInfo('STREAM', $streamKey);
    }

    /**
     * 获取消费者组信息
     *
     * @param string $streamKey Stream键名
     * @return array 返回消费者组信息
     */
    public static function redisStreamGroupInfo(string $streamKey): array
    {
        return self::redis()->xInfo('GROUPS', $streamKey);
    }

    /**
     * 获取消费者信息
     *
     * @param string $streamKey Stream键名
     * @param string $consumerGroup 消费者组名
     * @return array 返回消费者信息
     */
    public static function redisStreamConsumerInfo(string $streamKey, string $consumerGroup): array
    {
        return self::redis()->xInfo('CONSUMERS', $streamKey, $consumerGroup);
    }

}